<!DOCTYPE html>



  


<html class="theme-next muse use-motion" lang="zh-Hans">
<head><meta name="generator" content="Hexo 3.8.0">
  <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform">
<meta http-equiv="Cache-Control" content="no-siteapp">
















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css">







<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css">

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css">


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="Hexo, NexT">





  <link rel="alternate" href="/atom.xml" title="renhao" type="application/atom+xml">






<meta name="description" content="本文探讨一下 webpack 中的 loader 、plugin 的使用与原理">
<meta property="og:type" content="article">
<meta property="og:title" content="webpack 中的 loader 与 plugin">
<meta property="og:url" content="https://huangrenhao.gitee.io/2020/09/03/webpack-loader-plugin/index.html">
<meta property="og:site_name" content="renhao">
<meta property="og:description" content="本文探讨一下 webpack 中的 loader 、plugin 的使用与原理">
<meta property="og:locale" content="zh-Hans">
<meta property="og:updated_time" content="2020-09-03T08:50:37.000Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="webpack 中的 loader 与 plugin">
<meta name="twitter:description" content="本文探讨一下 webpack 中的 loader 、plugin 的使用与原理">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Muse',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="https://huangrenhao.gitee.io/2020/09/03/webpack-loader-plugin/">





  <title>webpack 中的 loader 与 plugin | renhao</title>
  








<link rel="stylesheet" href="/css/prism-tomorrow.css" type="text/css"></head>

<body itemscope="" itemtype="http://schema.org/WebPage" lang="zh-Hans">
  
  
    
  

  <div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>

    <header id="header" class="header" itemscope="" itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/" class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">renhao</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle"></p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br>
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br>
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br>
            
            归档
          </a>
        </li>
      

      
    </ul>
  

  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope="" itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://huangrenhao.gitee.io/2020/09/03/webpack-loader-plugin/">

    <span hidden itemprop="author" itemscope="" itemtype="http://schema.org/Person">
      <meta itemprop="name" content="renhao">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope="" itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="renhao">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">webpack 中的 loader 与 plugin</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2020-09-03T16:39:30+08:00">
                2020-09-03
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope="" itemtype="http://schema.org/Thing">
                  <a href="/categories/webpack/" itemprop="url" rel="index">
                    <span itemprop="name">webpack</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2020/09/03/webpack-loader-plugin/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count valine-comment-count" data-xid="/2020/09/03/webpack-loader-plugin/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          
            <div class="post-wordcount">
              
                
                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>
                
                  <span class="post-meta-item-text">字数统计&#58;</span>
                
                <span title="字数统计">
                  4.7k
                </span>
              

              
                <span class="post-meta-divider">|</span>
              

              
                <span class="post-meta-item-icon">
                  <i class="fa fa-clock-o"></i>
                </span>
                
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                
                <span title="阅读时长">
                  17
                </span>
              
            </div>
          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <p>本文探讨一下 webpack 中的 loader 、plugin 的使用与原理<a id="more"></a></p>
<blockquote>
<p>参考<a href="https://www.jianshu.com/p/c021b78c9ef2" target="_blank" rel="noopener">webpack原理：编写 loader 和 plugin</a></p>
</blockquote>
<h2 id="编写-Loader"><a href="#编写-Loader" class="headerlink" title="编写 Loader"></a>编写 Loader</h2><p><code>Loader</code>就像是一个翻译员，能把源文件经过转化后输出新的结果，并且一个文件还可以链式的经过多个翻译员翻译。<br> 以处理SCSS文件为例：</p>
<ol>
<li>SCSS源代码会先交给<code>sass-loader</code>把SCSS转换成CSS；</li>
<li>把<code>sass-loader</code>输出的CSS交给<code>css-loader</code>处理，<strong>找出CSS中依赖的资源、压缩CSS等</strong>；</li>
<li>把<code>css-loader</code>输出的CSS交给<code>style-loader</code>处理，<strong>转换成通过脚本加载的JavaScript代码</strong>；</li>
</ol>
<p>可以看出以上的处理过程需要有顺序的链式执行，先<code>sass-loader</code>再<code>css-loader</code>再<code>style-loader</code>。 以上处理的Webpack相关配置如下：</p>
<pre class=" language-tsx"><code class="language-tsx">module.exports = {
  module: {
    rules: [
      {
        // 增加对 SCSS 文件的支持
        test: /\.scss/,
        // SCSS 文件的处理顺序为先 sass-loader 再 css-loader 再 style-loader
        use: [
          'style-loader',
          {
            loader:'css-loader',
            // 给 css-loader 传入配置项
            options:{
              minimize:true, 
            }
          },
          'sass-loader'],
      },
    ]
  },
};
</code></pre>
<h4 id="Loader的职责"><a href="#Loader的职责" class="headerlink" title="Loader的职责"></a>Loader的职责</h4><p>由上面的例子可以看出：一个<code>Loader</code>的职责是单一的，只需要完成一种转换。 如果一个源文件需要经历多步转换才能正常使用，就通过多个<code>Loader</code>去转换。 在调用多个<code>Loader</code>去转换一个文件时，每个<code>Loader</code>会链式的顺序执行， 第一个Loader将会拿到需处理的原内容，上一个<code>Loader</code>处理后的结果会传给下一个接着处理，最后的<code>Loader</code>将处理后的最终结果返回给Webpack。<br> 所以，在你开发一个<code>Loader</code>时，请保持其职责的单一性，你只需关心输入和输出。</p>
<h4 id="Loader基础"><a href="#Loader基础" class="headerlink" title="Loader基础"></a>Loader基础</h4><p>由于Webpack是运行在Node.js之上的，一个<code>Loader</code>其实就是一个Node.js模块，这个模块需要导出一个函数。 这个导出的函数的工作就是获得处理前的原内容，对原内容执行处理后，返回处理后的内容。<br> 一个最简单的<code>Loader</code>的源码如下：</p>
<pre class=" language-jsx"><code class="language-jsx">module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span>source<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment" spellcheck="true">// source 为 compiler 传递给 Loader 的一个文件的原内容</span>
  <span class="token comment" spellcheck="true">// 该函数需要返回处理后的内容，这里简单起见，直接把原内容返回了，相当于该`Loader`没有做任何转换</span>
  <span class="token keyword">return</span> source<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
<p>由于<code>Loader</code>运行在Node.js中，你可以调用任何Node.js自带的API，或者安装第三方模块进行调用：</p>
<pre class=" language-jsx"><code class="language-jsx"><span class="token keyword">const</span> sass <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'node-sass'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span>source<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">return</span> <span class="token function">sass</span><span class="token punctuation">(</span>source<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
<h4 id="Loader进阶"><a href="#Loader进阶" class="headerlink" title="Loader进阶"></a>Loader进阶</h4><p>Webpack还提供一些API供<code>Loader</code>调用。</p>
<h5 id="获得Loader的options"><a href="#获得Loader的options" class="headerlink" title="获得Loader的options"></a>获得Loader的options</h5><p>在最上面处理SCSS文件的Webpack配置中，给<code>css-loader</code>传了<code>options</code>参数，以控制<code>css-loader</code>。要在自己编写的Loader中获取到用户传入的<code>options</code>，需要这样做：</p>
<pre class=" language-jsx"><code class="language-jsx"><span class="token keyword">const</span> loaderUtils <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'loader-utils'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span>source<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment" spellcheck="true">// 获取到用户给当前 Loader 传入的 options</span>
  <span class="token keyword">const</span> options <span class="token operator">=</span> loaderUtils<span class="token punctuation">.</span><span class="token function">getOptions</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> source<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
<h5 id="返回其它结果"><a href="#返回其它结果" class="headerlink" title="返回其它结果"></a>返回其它结果</h5><p>上面的<code>Loader</code>都只是返回了原内容转换后的内容，但有些场景下还需要返回除了内容之外的东西。<br> 例如以用<code>babel-loader</code>转换ES6代码为例，它还需要输出转换后的ES5代码对应的Source Map，以方便调试源码。 为了把Source Map也一起随着ES5代码返回给Webpack，可以这样写：</p>
<pre class=" language-jsx"><code class="language-jsx">module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span>source<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment" spellcheck="true">// 通过 this.callback 告诉 Webpack 返回的结果</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">callback</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span> source<span class="token punctuation">,</span> sourceMaps<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token comment" spellcheck="true">// 当你使用 this.callback 返回内容时，该 Loader 必须返回 undefined，</span>
  <span class="token comment" spellcheck="true">// 以让 Webpack 知道该 Loader 返回的结果在 this.callback 中，而不是 return 中 </span>
  <span class="token keyword">return</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
<p>其中的<code>this.callback</code>是Webpack给<code>Loader</code>注入的API，以方便<code>Loader</code>和Webpack之间通信。<code>this.callback</code>的详细使用方法如下：</p>
<pre class=" language-tsx"><code class="language-tsx">this.callback(
    // 当无法转换原内容时，给 Webpack 返回一个 Error
    err: Error | null,
    // 原内容转换后的内容
    content: string | Buffer,
    // 用于把转换后的内容得出原内容的 Source Map，方便调试
    sourceMap?: SourceMap,
    // 如果本次转换为原内容生成了 AST 语法树，可以把这个 AST 返回，
    // 以方便之后需要 AST 的 Loader 复用该 AST，以避免重复生成 AST，提升性能
    abstractSyntaxTree?: AST
);
</code></pre>
<p>Source Map的生成很耗时，通常在开发环境下才会生成Source Map，其它环境下不用生成，以加速构建。 为此Webpack为<code>Loader</code>提供了<code>this.sourceMap</code> API去告诉<code>Loader</code>当前构建环境下用户是否需要Source Map。</p>
<h5 id="同步与异步"><a href="#同步与异步" class="headerlink" title="同步与异步"></a>同步与异步</h5><p><code>Loader</code>有同步和异步之分，上面介绍的<code>Loader</code>都是同步的<code>Loader</code>，因为它们的转换流程都是同步的，转换完成后再返回结果。 但在有些场景下转换的步骤只能是异步完成的，例如你需要通过网络请求才能得出结果，如果采用同步的方式网络请求就会阻塞整个构建，导致构建非常缓慢。<br> 在转换步骤是异步时，你可以这样：</p>
<pre class=" language-jsx"><code class="language-jsx">module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span>source<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment" spellcheck="true">// 告诉 Webpack 本次转换是异步的，Loader 会在 callback 中回调结果</span>
    <span class="token keyword">var</span> callback <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token keyword">async</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token function">someAsyncOperation</span><span class="token punctuation">(</span>source<span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span>err<span class="token punctuation">,</span> result<span class="token punctuation">,</span> sourceMaps<span class="token punctuation">,</span> ast<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment" spellcheck="true">// 通过 callback 返回异步执行后的结果</span>
        <span class="token function">callback</span><span class="token punctuation">(</span>err<span class="token punctuation">,</span> result<span class="token punctuation">,</span> sourceMaps<span class="token punctuation">,</span> ast<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
<h5 id="处理二进制数据"><a href="#处理二进制数据" class="headerlink" title="处理二进制数据"></a>处理二进制数据</h5><p>在默认的情况下，Webpack传给<code>Loader</code>的原内容都是UTF-8格式编码的字符串。 但有些场景下<code>Loader</code>不是处理文本文件，而是处理二进制文件，例如<code>file-loader</code>，就需要Webpack给<code>Loader</code>传入二进制格式的数据。 为此，你需要这样编写<code>Loader</code>：</p>
<pre class=" language-jsx"><code class="language-jsx">module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span>source<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment" spellcheck="true">// 在 exports.raw === true 时，Webpack 传给 Loader 的 source 是 Buffer 类型的</span>
    source <span class="token keyword">instanceof</span> <span class="token class-name">Buffer</span> <span class="token operator">===</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
    <span class="token comment" spellcheck="true">// Loader 返回的类型也可以是 Buffer 类型的</span>
    <span class="token comment" spellcheck="true">// 在 exports.raw !== true 时，Loader 也可以返回 Buffer 类型的结果</span>
    <span class="token keyword">return</span> source<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token comment" spellcheck="true">// 通过 exports.raw 属性告诉 Webpack 该 Loader 是否需要二进制数据 </span>
module<span class="token punctuation">.</span>exports<span class="token punctuation">.</span>raw <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
</code></pre>
<p><strong>以上代码中最关键的代码是最后一行<code>module.exports.raw = true;</code>，没有该行<code>Loader</code>只能拿到字符串。</strong></p>
<h5 id="缓存加速"><a href="#缓存加速" class="headerlink" title="缓存加速"></a>缓存加速</h5><p>在有些情况下，有些转换操作需要大量计算非常耗时，如果每次构建都重新执行重复的转换操作，构建将会变得非常缓慢。为此，Webpack会默认缓存所有<code>Loader</code>的处理结果，也就是说在需要被处理的文件或者其依赖的文件没有发生变化时， 是不会重新调用对应的<code>Loader</code>去执行转换操作的。<br> 如果想让Webpack不缓存该<code>Loader</code>的处理结果，可以这样：</p>
<pre class=" language-jsx"><code class="language-jsx">module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token keyword">function</span><span class="token punctuation">(</span>source<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment" spellcheck="true">// 关闭该 Loader 的缓存功能</span>
  <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">cacheable</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">return</span> source<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
</code></pre>
<h4 id="其它Loader-API"><a href="#其它Loader-API" class="headerlink" title="其它Loader API"></a>其它Loader API</h4><p>除了以上提到的在<code>Loader</code>中能调用的Webpack API外，还存在以下常用API：</p>
<ul>
<li><code>this.context</code>：当前处理文件的所在目录，假如当前<code>Loader</code>处理的文件是<code>/src/main.js</code>，则<code>this.context</code>就等于<code>/src</code>。</li>
<li><code>this.resource</code>：当前处理文件的完整请求路径，包括<code>querystring</code>，例如<code>/src/main.js?name=1</code>。</li>
<li><code>this.resourcePath</code>：当前处理文件的路径，例如<code>/src/main.js</code>。</li>
<li><code>this.resourceQuery</code>：当前处理文件的<code>querystring</code>。</li>
<li><code>this.target</code>：等于Webpack配置中的<code>Target</code>。</li>
<li><code>this.loadModule</code>：当<code>Loader</code>在处理一个文件时，如果依赖其它文件的处理结果才能得出当前文件的结果时， 就可以通过<code>this.loadModule(request: string, callback: function(err, source, sourceMap, module))</code>去获得<code>request</code>对应文件的处理结果。</li>
<li><code>this.resolve</code>：像<code>require</code>语句一样获得指定文件的完整路径，使用方法为<code>resolve(context: string, request: string, callback: function(err, result: string))</code>。</li>
<li><code>this.addDependency</code>：给当前处理文件添加其依赖的文件，以便再其依赖的文件发生变化时，会重新调用<code>Loader</code>处理该文件。使用方法为<code>addDependency(file: string)</code>。</li>
<li><code>this.addContextDependency</code>：和<code>addDependency</code>类似，但<code>addContextDependency</code>是把整个目录加入到当前正在处理文件的依赖中。使用方法为<code>addContextDependency(directory: string)</code>。</li>
<li><code>this.clearDependencies</code>：清除当前正在处理文件的所有依赖，使用方法为<code>clearDependencies()</code>。</li>
<li><code>this.emitFile</code>：输出一个文件，使用方法为<code>emitFile(name: string, content: Buffer|string, sourceMap: {...})</code>。</li>
</ul>
<h4 id="加载本地Loader"><a href="#加载本地Loader" class="headerlink" title="加载本地Loader"></a>加载本地Loader</h4><p>在开发<code>Loader</code>的过程中，为了测试编写的<code>Loader</code>是否能正常工作，需要把它配置到Webpack中后，才可能会调用该<code>Loader</code>。使用的<code>Loader</code>都是通过Npm安装的，要使用<code>Loader</code>时会直接使用<code>Loader</code>的名称，代码如下：</p>
<pre class=" language-tsx"><code class="language-tsx">module.exports = {
  module: {
    rules: [
      {
        test: /\.css/,
        use: ['style-loader'],
      },
    ]
  },
};
</code></pre>
<p>如果还采取以上的方法去使用本地开发的<code>Loader</code>将会很麻烦，因为你需要确保编写的<code>Loader</code>的源码是在<code>node_modules</code>目录下。 为此你需要先把编写的<code>Loader</code>发布到Npm仓库后再安装到本地项目使用。<br> 解决以上问题的便捷方法有两种，分别如下：</p>
<h5 id="Npm-link"><a href="#Npm-link" class="headerlink" title="Npm link"></a>Npm link</h5><p>Npm link专门用于开发和调试本地Npm模块，能做到在不发布模块的情况下，把本地的一个正在开发的模块的源码链接到项目的<code>node_modules</code>目录下，让项目可以直接使用本地的Npm模块。 由于是通过软链接的方式实现的，编辑了本地的Npm模块代码，在项目中也能使用到编辑后的代码。<br> 完成Npm link的步骤如下：</p>
<ol>
<li>确保正在开发的本地Npm模块（也就是正在开发的Loader）的<code>package.json</code>已经正确配置好；</li>
<li>在本地Npm模块根目录下执行<code>npm link</code>，把本地模块注册到全局；</li>
<li>在项目根目录下执行<code>npm link loader-name</code>，把第2步注册到全局的本地Npm模块链接到项目的<code>node_moduels</code>下，其中的<code>loader-name</code>是指在第1步中的<code>package.json</code>文件中配置的模块名称。</li>
</ol>
<p>链接好<code>Loader</code>到项目后你就可以像使用一个真正的 Npm 模块一样使用本地的<code>Loader</code>了。</p>
<h5 id="ResolveLoader"><a href="#ResolveLoader" class="headerlink" title="ResolveLoader"></a>ResolveLoader</h5><p><code>ResolveLoader</code>用于配置Webpack如何寻找<code>Loader</code>。 默认情况下只会去<code>node_modules</code>目录下寻找，为了让Webpack加载放在本地项目中的<code>Loader</code>需要修改<code>resolveLoader.modules</code>。<br> 假如本地的<code>Loader</code>在项目目录中的<code>./loaders/loader-name</code>中，则需要如下配置：</p>
<pre class=" language-java"><code class="language-java">module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  resolveLoader<span class="token operator">:</span><span class="token punctuation">{</span>
    <span class="token comment" spellcheck="true">// 去哪些目录下寻找 Loader，有先后顺序之分</span>
    modules<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'node_modules'</span><span class="token punctuation">,</span><span class="token string">'./loaders/'</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<p>加上以上配置后，Webpack会先去<code>node_modules</code>项目下寻找<code>Loader</code>，如果找不到，会再去<code>./loaders/</code>目录下寻找。</p>
<h2 id="编写Plugin"><a href="#编写Plugin" class="headerlink" title="编写Plugin"></a>编写Plugin</h2><p>Webpack通过<code>Plugin</code>机制让其更加灵活，以适应各种应用场景。 在<strong>Webpack运行的生命周期中会广播出许多事件</strong>，<code>Plugin</code>可以监听这些事件，在合适的时机通过Webpack提供的API改变输出结果。<br> 一个最基础的<code>Plugin</code>的代码是这样的：</p>
<pre class=" language-jsx"><code class="language-jsx"><span class="token keyword">class</span> <span class="token class-name">BasicPlugin</span><span class="token punctuation">{</span>
  <span class="token comment" spellcheck="true">// 在构造函数中获取用户给该插件传入的配置</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span><span class="token punctuation">{</span>
  <span class="token punctuation">}</span>

  <span class="token comment" spellcheck="true">// Webpack 会调用 BasicPlugin 实例的 apply 方法给插件实例传入 compiler 对象</span>
  <span class="token function">apply</span><span class="token punctuation">(</span>compiler<span class="token punctuation">)</span><span class="token punctuation">{</span>
    compiler<span class="token punctuation">.</span><span class="token function">plugin</span><span class="token punctuation">(</span><span class="token string">'compilation'</span><span class="token punctuation">,</span><span class="token keyword">function</span><span class="token punctuation">(</span>compilation<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token comment" spellcheck="true">// 导出 Plugin</span>
module<span class="token punctuation">.</span>exports <span class="token operator">=</span> BasicPlugin<span class="token punctuation">;</span>
</code></pre>
<p>在使用这个<code>Plugin</code>时，相关配置代码如下：</p>
<pre class=" language-tsx"><code class="language-tsx">const BasicPlugin = require('./BasicPlugin.js');
module.export = {
  plugins:[
    new BasicPlugin(options),
  ]
}
</code></pre>
<p>Webpack启动后，在读取配置的过程中会先执行<code>new BasicPlugin(options)</code>初始化一个<code>BasicPlugin</code>获得其实例。 在初始化<code>compiler</code>对象后，再调用<code>basicPlugin.apply(compiler)</code>给插件实例传入<code>compiler</code>对象。 插件实例在获取到<code>compiler</code>对象后，就可以通过<code>compiler.plugin(事件名称, 回调函数)</code>监听到Webpack广播出来的事件。 并且可以通过<code>compiler</code>对象去操作Webpack。</p>
<h4 id="Compiler和Compilation"><a href="#Compiler和Compilation" class="headerlink" title="Compiler和Compilation"></a>Compiler和Compilation</h4><p>在开发<code>Plugin</code>时最常用的两个对象就是<code>Compiler</code>和<code>Compilation</code>，它们是<code>Plugin</code>和Webpack之间的桥梁。<code>Compiler</code>和<code>Compilation</code>的含义如下：</p>
<ul>
<li><strong><code>Compiler</code>对象</strong>包含了<strong>Webpack环境所有的的配置信息</strong>，包含<code>options</code>，<code>loaders</code>，<code>plugins</code>这些信息，这个对象在Webpack启动时候被实例化，它是<strong>全局唯一</strong>的，可以简单地把它理解为<strong>Webpack实例</strong>；</li>
<li><strong><code>Compilation</code>对象</strong>包含了当前的<strong>模块资源、编译生成资源、变化的文件</strong>等。<strong>当Webpack以开发模式运行时，每当检测到一个文件变化，一次新的<code>Compilation</code>将被创建</strong>。<code>Compilation</code>对象也提供了很多事件回调供插件做扩展。通过<code>Compilation</code>也能读取到<code>Compiler</code>对象。</li>
</ul>
<p><code>Compiler</code>和<code>Compilation</code>的区别在于：<code>Compiler</code>代表了整个Webpack从启动到关闭的生命周期，而<code>Compilation</code>只是代表了一次新的编译。</p>
<h4 id="事件流"><a href="#事件流" class="headerlink" title="事件流"></a>事件流</h4><p>Webpack就像一条生产线，要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的，多个流程之间有存在依赖关系，只有完成当前处理后才能交给下一个流程去处理。 插件就像是一个插入到生产线中的一个功能，在特定的时机对生产线上的资源做处理。<br> Webpack通过Tapable来组织这条复杂的生产线。 Webpack在运行过程中会广播事件，插件只需要监听它所关心的事件，就能加入到这条生产线中，去改变生产线的运作。 Webpack的事件流机制保证了插件的有序性，使得整个系统扩展性很好。<br> Webpack的事件流机制应用了观察者模式，和Node.js中的<code>EventEmitter</code>非常相似。<code>Compiler</code>和<code>Compilation</code>都继承自<code>Tapable</code>，可以直接在<code>Compiler</code>和<code>Compilation</code>对象上广播和监听事件，方法如下：</p>
<pre class=" language-dart"><code class="language-dart"><span class="token comment" spellcheck="true">/**
* 广播出事件
* event-name 为事件名称，注意不要和现有的事件重名
* params 为附带的参数
*/</span>
compiler<span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span><span class="token string">'event-name'</span><span class="token punctuation">,</span>params<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment" spellcheck="true">/**
* 监听名称为 event-name 的事件，当 event-name 事件发生时，函数就会被执行。
* 同时函数中的 params 参数为广播事件时附带的参数。
*/</span>
compiler<span class="token punctuation">.</span><span class="token function">plugin</span><span class="token punctuation">(</span><span class="token string">'event-name'</span><span class="token punctuation">,</span><span class="token function">function</span><span class="token punctuation">(</span>params<span class="token punctuation">)</span> <span class="token punctuation">{</span>

<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<p>同理，<code>compilation.apply</code>和<code>compilation.plugin</code>使用方法和上面一致。<br> 在开发插件时，你可能会不知道该如何下手，因为你不知道该监听哪个事件才能完成任务。<br> 在开发插件时，还需要注意以下两点：</p>
<ul>
<li>只要能拿到<code>Compiler</code>或<code>Compilation</code>对象，就能广播出新的事件，所以在新开发的插件中也能广播出事件，给其它插件监听使用。</li>
<li>传给每个插件的<code>Compiler</code>和<code>Compilation</code>对象都是同一个引用。也就是说在一个插件中修改了<code>Compiler</code>或<code>Compilation</code>对象上的属性，会影响到后面的插件。</li>
<li>有些事件是异步的，这些异步的事件会附带两个参数，第二个参数为回调函数，在插件处理完任务时需要调用回调函数通知Webpack，才会进入下一处理流程。例如：</li>
</ul>
<pre class=" language-jsx"><code class="language-jsx">compiler<span class="token punctuation">.</span><span class="token function">plugin</span><span class="token punctuation">(</span><span class="token string">'emit'</span><span class="token punctuation">,</span><span class="token keyword">function</span><span class="token punctuation">(</span>compilation<span class="token punctuation">,</span> callback<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token comment" spellcheck="true">// 支持处理逻辑</span>
  <span class="token comment" spellcheck="true">// 处理完毕后执行 callback 以通知 Webpack </span>
  <span class="token comment" spellcheck="true">// 如果不执行 callback，运行流程将会一直卡在这不往下执行 </span>
  <span class="token function">callback</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre>
<h4 id="常用API"><a href="#常用API" class="headerlink" title="常用API"></a>常用API</h4><p>插件可以用来修改输出文件、增加输出文件、甚至可以提升Webpack性能、等等，总之插件通过调用 Webpack提供的API能完成很多事情。 由于Webpack提供的API非常多，有很多API很少用的上，又加上篇幅有限，下面来介绍一些常用的API。</p>
<h4 id="读取输出资源、代码块、模块及其依赖"><a href="#读取输出资源、代码块、模块及其依赖" class="headerlink" title="读取输出资源、代码块、模块及其依赖"></a>读取输出资源、代码块、模块及其依赖</h4><p>有些插件可能需要读取Webpack的处理结果，例如输出资源、代码块、模块及其依赖，以便做下一步处理。<br> 在<code>emit</code>事件发生时，代表源文件的转换和组装已经完成，在这里可以读取到最终将输出的资源、代码块、模块及其依赖，并且可以修改输出资源的内容。 插件代码如下：</p>
<pre class=" language-php"><code class="language-php"><span class="token keyword">class</span> <span class="token class-name">Plugin</span> <span class="token punctuation">{</span>
  <span class="token function">apply</span><span class="token punctuation">(</span>compiler<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    compiler<span class="token punctuation">.</span><span class="token function">plugin</span><span class="token punctuation">(</span><span class="token string">'emit'</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span>compilation<span class="token punctuation">,</span> callback<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token comment" spellcheck="true">// compilation.chunks 存放所有代码块，是一个数组</span>
      compilation<span class="token punctuation">.</span>chunks<span class="token punctuation">.</span><span class="token keyword">forEach</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span>chunk<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment" spellcheck="true">// chunk 代表一个代码块</span>
        <span class="token comment" spellcheck="true">// 代码块由多个模块组成，通过 chunk.forEachModule 能读取组成代码块的每个模块</span>
        chunk<span class="token punctuation">.</span><span class="token function">forEachModule</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span>module<span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token comment" spellcheck="true">// module 代表一个模块</span>
          <span class="token comment" spellcheck="true">// module.fileDependencies 存放当前模块的所有依赖的文件路径，是一个数组</span>
          module<span class="token punctuation">.</span>fileDependencies<span class="token punctuation">.</span><span class="token keyword">forEach</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span>filepath<span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment" spellcheck="true">// Webpack 会根据 Chunk 去生成输出的文件资源，每个 Chunk 都对应一个及其以上的输出文件</span>
        <span class="token comment" spellcheck="true">// 例如在 Chunk 中包含了 CSS 模块并且使用了 ExtractTextPlugin 时，</span>
        <span class="token comment" spellcheck="true">// 该 Chunk 就会生成 .js 和 .css 两个文件</span>
        chunk<span class="token punctuation">.</span>files<span class="token punctuation">.</span><span class="token keyword">forEach</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span>filename<span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token comment" spellcheck="true">// compilation.assets 存放当前所有即将输出的资源</span>
          <span class="token comment" spellcheck="true">// 调用一个输出资源的 source() 方法能获取到输出资源的内容</span>
          let source <span class="token operator">=</span> compilation<span class="token punctuation">.</span>assets<span class="token punctuation">[</span>filename<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">source</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

      <span class="token comment" spellcheck="true">// 这是一个异步事件，要记得调用 callback 通知 Webpack 本次事件监听处理结束。</span>
      <span class="token comment" spellcheck="true">// 如果忘记了调用 callback，Webpack 将一直卡在这里而不会往后执行。</span>
      <span class="token function">callback</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
<h4 id="监听文件变化"><a href="#监听文件变化" class="headerlink" title="监听文件变化"></a>监听文件变化</h4><p>Webpack会从配置的入口模块出发，依次找出所有的依赖模块，当入口模块或者其依赖的模块发生变化时， 就会触发一次新的<code>Compilation</code>。<br> 在开发插件时经常需要知道是哪个文件发生变化导致了新的<code>Compilation</code>，为此可以使用如下代码：</p>
<pre class=" language-tsx"><code class="language-tsx">// 当依赖的文件发生变化时会触发 watch-run 事件
compiler.plugin('watch-run', (watching, callback) => {
    // 获取发生变化的文件列表
    const changedFiles = watching.compiler.watchFileSystem.watcher.mtimes;
    // changedFiles 格式为键值对，键为发生变化的文件路径。
    if (changedFiles[filePath] !== undefined) {
      // filePath 对应的文件发生了变化
    }
    callback();
});
</code></pre>
<p>默认情况下Webpack只会监视入口和其依赖的模块是否发生变化，在有些情况下项目可能需要引入新的文件，例如引入一个HTML文件。 由于 JavaScript 文件不会去导入HTML文件，Webpack就不会监听HTML文件的变化，编辑HTML文件时就不会重新触发新的<code>Compilation</code>。 为了监听HTML文件的变化，我们需要把HTML文件加入到依赖列表中，为此可以使用如下代码：</p>
<pre class=" language-tsx"><code class="language-tsx">compiler.plugin('after-compile', (compilation, callback) => {
  // 把 HTML 文件添加到文件依赖列表，好让 Webpack 去监听 HTML 模块文件，在 HTML 模版文件发生变化时重新启动一次编译
    compilation.fileDependencies.push(filePath);
    callback();
});
</code></pre>
<h4 id="修改输出资源"><a href="#修改输出资源" class="headerlink" title="修改输出资源"></a>修改输出资源</h4><p>有些场景下插件需要修改、增加、删除输出的资源，要做到这点需要监听<code>emit</code>事件，因为发生<code>emit</code>事件时所有模块的转换和代码块对应的文件已经生成好， 需要输出的资源即将输出，因此<code>emit</code>事件是修改Webpack输出资源的最后时机。<br> 所有需要输出的资源会存放在<code>compilation.assets</code>中，<code>compilation.assets</code>是一个键值对，键为需要输出的文件名称，值为文件对应的内容。<br> 设置<code>compilation.assets</code>的代码如下：</p>
<pre class=" language-tsx"><code class="language-tsx">compiler.plugin('emit', (compilation, callback) => {
  // 设置名称为 fileName 的输出资源
  compilation.assets[fileName] = {
    // 返回文件内容
    source: () => {
      // fileContent 既可以是代表文本文件的字符串，也可以是代表二进制文件的 Buffer
      return fileContent;
      },
    // 返回文件大小
      size: () => {
      return Buffer.byteLength(fileContent, 'utf8');
    }
  };
  callback();
});
</code></pre>
<p>读取<code>compilation.assets</code>的代码如下：</p>
<pre class=" language-tsx"><code class="language-tsx">compiler.plugin('emit', (compilation, callback) => {
  // 读取名称为 fileName 的输出资源
  const asset = compilation.assets[fileName];
  // 获取输出资源的内容
  asset.source();
  // 获取输出资源的文件大小
  asset.size();
  callback();
});
</code></pre>

      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/2020/09/03/webpack-lifecycle/" rel="next" title="webpack 原理探究（进行中）">
                <i class="fa fa-chevron-left"></i> webpack 原理探究（进行中）
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/2020/09/03/browser-repaint-reflow/" rel="prev" title="浏览器中的 painting 和 layout">
                浏览器中的 painting 和 layout <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          

  
    <div class="comments" id="comments">
    </div>
  



        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope="" itemtype="http://schema.org/Person">
            
              <p class="site-author-name" itemprop="name">renhao</p>
              <p class="site-description motion-element" itemprop="description"></p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives/">
              
                  <span class="site-state-item-count">94</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/categories/index.html">
                  <span class="site-state-item-count">19</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            

          </nav>

          
            <div class="feed-link motion-element">
              <a href="/atom.xml" rel="alternate">
                <i class="fa fa-rss"></i>
                RSS
              </a>
            </div>
          

          

          
          

          
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#编写-Loader"><span class="nav-number">1.</span> <span class="nav-text">编写 Loader</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#Loader的职责"><span class="nav-number">1.0.1.</span> <span class="nav-text">Loader的职责</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Loader基础"><span class="nav-number">1.0.2.</span> <span class="nav-text">Loader基础</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#Loader进阶"><span class="nav-number">1.0.3.</span> <span class="nav-text">Loader进阶</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#获得Loader的options"><span class="nav-number">1.0.3.1.</span> <span class="nav-text">获得Loader的options</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#返回其它结果"><span class="nav-number">1.0.3.2.</span> <span class="nav-text">返回其它结果</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#同步与异步"><span class="nav-number">1.0.3.3.</span> <span class="nav-text">同步与异步</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#处理二进制数据"><span class="nav-number">1.0.3.4.</span> <span class="nav-text">处理二进制数据</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#缓存加速"><span class="nav-number">1.0.3.5.</span> <span class="nav-text">缓存加速</span></a></li></ol></li><li class="nav-item nav-level-4"><a class="nav-link" href="#其它Loader-API"><span class="nav-number">1.0.4.</span> <span class="nav-text">其它Loader API</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#加载本地Loader"><span class="nav-number">1.0.5.</span> <span class="nav-text">加载本地Loader</span></a><ol class="nav-child"><li class="nav-item nav-level-5"><a class="nav-link" href="#Npm-link"><span class="nav-number">1.0.5.1.</span> <span class="nav-text">Npm link</span></a></li><li class="nav-item nav-level-5"><a class="nav-link" href="#ResolveLoader"><span class="nav-number">1.0.5.2.</span> <span class="nav-text">ResolveLoader</span></a></li></ol></li></ol></li></ol><li class="nav-item nav-level-2"><a class="nav-link" href="#编写Plugin"><span class="nav-number">2.</span> <span class="nav-text">编写Plugin</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#Compiler和Compilation"><span class="nav-number">2.0.1.</span> <span class="nav-text">Compiler和Compilation</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#事件流"><span class="nav-number">2.0.2.</span> <span class="nav-text">事件流</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#常用API"><span class="nav-number">2.0.3.</span> <span class="nav-text">常用API</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#读取输出资源、代码块、模块及其依赖"><span class="nav-number">2.0.4.</span> <span class="nav-text">读取输出资源、代码块、模块及其依赖</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#监听文件变化"><span class="nav-number">2.0.5.</span> <span class="nav-text">监听文件变化</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#修改输出资源"><span class="nav-number">2.0.6.</span> <span class="nav-text">修改输出资源</span></a></li></ol></li></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2020</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">renhao</span>

  
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-area-chart"></i>
    </span>
    
      <span class="post-meta-item-text">Site words total count&#58;</span>
    
    <span title="Site words total count">265.9k</span>
  
</div>


  <div class="powered-by">由 <a class="theme-link" target="_blank" href="https://hexo.io">Hexo</a> 强力驱动</div>



  <span class="post-meta-divider">|</span>



  <div class="theme-info">主题 &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Muse</a> v5.1.4</div>




        







        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  

  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  










  <script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
  <script src="//unpkg.com/valine/dist/Valine.min.js"></script>
  
  <script type="text/javascript">
    var GUEST = ['nick','mail','link'];
    var guest = 'nick,mail,link';
    guest = guest.split(',').filter(item=>{
      return GUEST.indexOf(item)>-1;
    });
    new Valine({
        el: '#comments' ,
        verify: false,
        notify: false,
        appId: '2P1xqUWiCoNm14MH4yhURlJi-gzGzoHsz',
        appKey: 'uJN0uagHIVgXtBO1OuLV9Ban',
        placeholder: 'Just go go',
        avatar:'mm',
        guest_info:guest,
        pageSize:'10' || 10,
    });
  </script>



  





  

  

  

  
  

  

  

  

</body>
</html>
